package math.statistics.distribution;

import graphics.Graphable2D;
import graphics.GraphingProperties;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import processor.MyException;
import processorIO.FileIO;

/**
 * The NormalDistribution class. A class for doing simple mathematical 
 * calculations involving normal distributions. Note that instances of this
 * class can be graphed using class graphing.GraphProgram.
 * 
 * Fully Tested: No
 * Fully Documented: Yes
 * Created: 11-Dec-2011
 * Last Updated: 13-Dec-2011
 *
 * @author Shimu Wu
 */
public class NormalDistribution extends StatisticalDistribution
        implements Graphable2D {

    /**
     * The Standard Normal distribution with mean 0, and standard 
     * deviation 1.
     */
    public static final NormalDistribution STANDARD_NORMAL =
            new NormalDistribution(0, 1);

    /** The mathematical constant e */
    private static final double e = 2.718281828459045;

    /** The mathematical constant pi */
    private static final double pi = 3.14159265358979323;

    private final double variance, denominator;

    private static double[][] areas = null;

    /**
     * Instantiates a NormalDistribution with the given mean and 
     * standardDeviation.
     * 
     * @param mean the mean of the NormalDistribution
     * @param standardDeviation the standard deviation of the 
     * NormalDistribution
     */
    public NormalDistribution(double mean, double standardDeviation) {
        this.mean = mean;
        this.standardDeviation = standardDeviation;
        this.variance = Math.pow(standardDeviation, 2);
        denominator = Math.sqrt(2 * pi * variance);
    }

    /**
     * Returns the mean of this Normal distribution
     *  
     * @return the mean of this Normal distribution
     */
    @Override
    public double getMean() {
        return mean;
    }

    /**
     * Returns the standard deviation of this Normal distribution
     * @return the standard deviation of this Normal distribution
     */
    @Override
    public double getStandardDeviation() {
        return standardDeviation;
    }

    /**
     * Returns the z-value of the given x-value.
     * @param x
     * @return the z-value of the given x-value.
     */
    public Double getZValue(double x) {
        if (standardDeviation == 0) {
            return null;
        }
        return (x - mean) / (standardDeviation);
    }

    /**
     * Returns the area under the Normal distribution curve to the left
     * of the given x value.
     * 
     * @param x
     * @return the area under the Normal distribution curve to the left
     * of the given x value.
     * 
     * @throws MyException if the file containing areas is not found
     */
    public Double getLeftTailArea(double x) throws FileNotFoundException,
            IOException {

        if (standardDeviation == 0) {
            return 0.0;
        }

        // Gets the z-value
        double z = (x - mean) / (standardDeviation);

        if (Math.abs(z) > 3.99) {
            return 0.0;
        }

        loadArea();

        // The z value needs to be rounded to 2 decimal places, i.e.
        // z = a.bc
        // The first index is ab
        // The second index is c 
        int index1, index2;
        // Multiply z by 10 to get ab.c, then round off
        index1 = Math.abs((int) (z * 10));

        // Multiply z by 100 to get abc, subtract from index1, which is ab0
        // This will get the value of c
        index2 = Math.abs(((int) (z * 100))) - index1 * 10;

        double area = areas[index1][index2];
        if (z < 0) {
            area = 1 - area;
        }

        return area;
    }

    /**
     * Loads the area table for the normal distribution. 
     */
    private void loadArea() throws FileNotFoundException, IOException {
        if (areas == null) {
            areas = new double[40][10];

            ArrayList<String> content =
                    FileIO.getContent("data/normal_dist_area.txt");

            String[] tokens;
            for (int i = 0; i < 40; i++) {
                tokens = content.get(i).split(",");
                for (int j = 0; j < 10; j++) {
                    areas[i][j] = Double.parseDouble("0." + tokens[j + 1]);
                }
            }
        }
    }

    @Override
    public Double evaluate(Double x) {
        double exponent = -Math.pow(x - mean, 2)
                / (2 * variance);
        return Math.pow(e, exponent) / denominator;
    }

    @Override
    public GraphingProperties getGraphProperties() {
        double xMin, xMax, yMin, yMax;

        xMin = mean - 4 * standardDeviation;
        xMax = mean + 4 * standardDeviation;

        double yRange, yMargin;

        yRange = 1.0 / denominator;
        yMargin = 0.1 * yRange;
        yMin = -yMargin;
        yMax = yRange + yMargin;

        return new GraphingProperties(xMin, xMax, yMin, yMax);
    }
}
